#include "Schematic.h"
#include "Chunk.h"

#include "..\Rendering\RendererBase.h"

Schematic::Schematic(const std::string &fileName, RendererBase *renderer) :
	Loaded(true),
	_renderer(renderer)
{
	LoadFromFile(fileName);
}

inline Chunk *Schematic::GetChunk(const Point2us &pt, const bool &doLoad)
{
	_mutex.Lock();
	auto it = _chunks.find(pt);
	_mutex.Unlock();

	if (it == _chunks.end())
	{
		if (!doLoad)
			return nullptr;

		Chunk *chunk = new Chunk();
		_mutex.Lock();
		_chunks.insert(std::make_pair(pt, chunk));

		// init the chunk
		for (byte z = 0; z < 16; ++z)
			for (uint16_t y = 0; y < Height; ++y)
				for (byte x = 0; x < 16; ++x)
				{
					if (((pt.X << 4) + x) >= Width ||
						((pt.Y << 4) + z) >= Length)
						continue;

					int blockNumber = (y * Width * Length) + (((pt.Y << 4) + z) * Width) + ((pt.X << 4) + x);
					Point3b localPt(x, y, z);
					Block &b = chunk->GetBlock(localPt);
						
					b.ID = _blockData->Value[blockNumber];
					b.Data = _dataData->Value[blockNumber];

					byte &d = chunk->GetData(localPt);
						
					if (_renderer->Renderers->IsFullBlock(b))
						d |= 0x01;
					if (_renderer->Renderers->IsOpaqueBlock(b))
						d |= 0x02;
				}
		_mutex.Unlock();

		return chunk;
	}

	return (*it).second;
}

const NBTTagBase *Schematic::GetTileEntity(const int &x, const int &y, const int &z)
{
	return _tileEntTags[(y * Width * Length) + (z * Width) + x];
}
	
const Block &Schematic::GetBlock(const int &x, const int &y, const int &z, const bool &forceLoad)
{
	if (x < 0 ||
		y < 0 ||
		z < 0)
		return Block::Empty;
	if (x >= Width ||
		y >= Height ||
		z >= Length)
		return Block::Empty;

	Point2us chunkPos(x >> 4, z >> 4);
	Chunk *chunk = GetChunk(chunkPos, forceLoad);

	if (!chunk)
		return Block::Empty;
		
	Point3b localPos(x - (chunkPos.X << 4), y, z - (chunkPos.Y << 4));
	return chunk->GetBlock(localPos);
}

const Block &Schematic::GetBlock(const int &x, const int &y, const int &z)
{
	return GetBlock(x, y, z, true);
}
	
bool Schematic::IsOpaqueBlockAt(const int &x, const int &y, const int &z)
{
	if (x < 0 ||
		y < 0 ||
		z < 0)
		return false;
	if (x >= Width ||
		y >= Height ||
		z >= Length)
		return false;

	Point2us chunkPos(x >> 4, z >> 4);
	Chunk *chunk = GetChunk(chunkPos);

	if (!chunk)
		return false;
		
	Point3b localPos(x - (chunkPos.X << 4), y, z - (chunkPos.Y << 4));
	return chunk->IsOpaque(localPos);
}

bool Schematic::IsFullBlockAt(const int &x, const int &y, const int &z)
{
	if (x < 0 ||
		y < 0 ||
		z < 0)
		return false;
	if (x >= Width ||
		y >= Height ||
		z >= Length)
		return false;

	Point2us chunkPos(x >> 4, z >> 4);
	Chunk *chunk = GetChunk(chunkPos);

	if (!chunk)
		return false;
		
	Point3b localPos(x - (chunkPos.X << 4), y, z - (chunkPos.Y << 4));
	return chunk->IsFull(localPos);
}

void Schematic::LoadFromFile(const std::string &fileName)
{
	NBTFile file;
	file.Load(fileName, true);

	if (file.Root == nullptr)
	{
		Loaded = false;
		return;
	}

	Width = file.Root->Find("Width")->As<NBTTagValue<short>>()->Value;
	Height = file.Root->Find("Height")->As<NBTTagValue<short>>()->Value;
	Length = file.Root->Find("Length")->As<NBTTagValue<short>>()->Value;

	WidthInChunks = (int)ceilf(Width / 16.0f);
	LengthInChunks = (int)ceilf(Length / 16.0f);
	TotalChunks = WidthInChunks * LengthInChunks;

	_blockData = file.Root->Find("Blocks")->As<NBTTagList<byte, TAG_Byte>>();
	_dataData = file.Root->Find("Data")->As<NBTTagList<byte, TAG_Byte>>();

//	int xx = blockNumber % Width;
//	int zz = (blockNumber / Width) % Length;
//	int yy = ((blockNumber / Width) / Length) % Height;

	auto tileEnts = file.Root->Find("TileEntities")->As<NBTTagCompound>();

	for (size_t i = 0; i < tileEnts->Value.size(); ++i)
	{
		auto tag = tileEnts->Value[i];
		Point3i pt(tag->Find("x")->As<NBTTagValue<int>>()->Value, tag->Find("y")->As<NBTTagValue<int>>()->Value, tag->Find("z")->As<NBTTagValue<int>>()->Value);
		int blockNumber = (pt.Y * Width * Length) + (pt.Z * Width) + pt.X;

		_tileEntTags[blockNumber] = tag;
	}
}